﻿module Preprocessor.Define

open System.Text
open Clexer
open History
open PrintUtils
open Prep

let saveDefine (context:Context) (prep:Prep) lexemes =
    let rec findDefineArgs lexemes args =
        let value =  prep.GetNextLexeme lexemes
        if value.IsSome then
            match value.Value with 
                | RPAREN _ -> args
                | IDENT (x, y)-> findDefineArgs lexemes (x :: args)
                | _ -> findDefineArgs lexemes args
        else failwith "Unknown number of args in define. Possibly right bracket is missed"

    let rec findDefine lexemes args expr isFirst =
        let value =  prep.GetNextLexeme lexemes
        if value.IsSome then
            match value.Value with 
                | DIRECTIVE_EOL(_) -> (List.length args, args, List.rev expr)
                | LPAREN _ when isFirst ->  findDefine lexemes (findDefineArgs lexemes []) expr false 
                | x -> findDefine lexemes args (x::expr) false
        else failwith "Define directive isn't closed"
    
    let value =  prep.GetNextLexeme lexemes
    if value.IsSome then
        match value.Value with 
            | IDENT (x,y) -> 
                let (argCount, args, text) = findDefine lexemes [] [] true
                if context.DefinesMap.ContainsKey x then
                    Logger.Warning "Preprocessor" ("redefine variable " + x)
                    context.DefinesMap.Remove(x) |>ignore
                context.DefinesMap.Add(x,(argCount, args, text))
                (x + (StringListToStringWithSeparator args ",") + " " + (TokenListToString text))            
            | _ -> failwith "Not Ident after #define"
    else failwith "Can't get next token"

let countDefineArgs (prep:Prep) lexemes = 
    let rec findArgs lexemes args currentArg=
        let value =  prep.GetNextLexeme lexemes
        if value.IsSome then
            match value.Value with 
                | RPAREN _ -> if currentArg<>[] then  currentArg::args else args
                | COMMA(_) -> findArgs lexemes ((List.rev currentArg)::args) []
                | X -> findArgs lexemes args (X::currentArg)
        else failwith "Unknown number of arguments in define"
    let value =  prep.GetNextLexeme lexemes
    if value.IsSome then
        match value.Value with 
            | LPAREN _ -> findArgs lexemes [] []
            | x -> prep.LexemeStack <- x::(prep.LexemeStack);
                   []
    else []

let substitute text args values =
    let subst item = 
        match item with
        | IDENT(x, y) ->
            if List.exists (fun t -> t = x) args then
                let ind = List.findIndex (fun t -> t = x) args
                (List.nth values ind)
            else [item]     
        | _ -> [item]
    List.concat (List.map subst text)

let trySubstituteDefine (prep:Prep) str loc (argNum, args, text) lexemes =
    if argNum =0 then
        let subst = text
        addHistory prep (str) (TokenListToString  subst) loc true
        prep.LexemeStack <- List.append subst (prep.LexemeStack)
        ()
    else
        let argVals = countDefineArgs prep lexemes
        if argNum <> argVals.Length 
        then failwithf "wrong number of args in define"
        else 
            let subst = (substitute text args argVals)
            addHistory prep (str + (StringListToStringWithSeparator (List.map TokenListToString argVals) ",")) (TokenListToString  subst) loc true
            prep.LexemeStack <- List.append subst (prep.LexemeStack)
            ()